home *** CD-ROM | disk | FTP | other *** search
/ Mobiclic 71 / MOBICLIC 71.ISO / mac / DATA / COMMUN / MOBI_ZAPETTE$$$.dir / 00003_Script_Custom Scrollbar < prev    next >
Text File  |  2004-12-23  |  37KB  |  1,047 lines

  1. -- DESCRIPTION --
  2.  
  3. on getBehaviorDescription me
  4.   return \
  5.     "CUSTOM SCROLL BAR" & RETURN & RETURN & \
  6.     "Create dynamic scrollbars with your own artwork. " & \
  7.     " Such scrollbars are not Direct-To-Stage, so other sprites can appear over the top of them." & RETURN & RETURN & \
  8.     "You will need to create four graphic members:" & RETURN & \
  9.     "+ up arrow" & RETURN & \
  10.     "+ down arrow" & RETURN & \
  11.     "+ dragger" & RETURN & \
  12.     "+ backing bar" & RETURN & RETURN & \
  13.     "You may wish to use two additional members, to indicate that the arrow buttons have been pressed:" & RETURN & \
  14.     "+ up arrow (pressed state)" & RETURN & \
  15.     "+ down arrow (pressed state)" & RETURN & RETURN & \
  16.     "Place the four standard members on the Stage, and drop this behavior onto each of them. " & \
  17.     " Choose how the current sprite is to act in the appropriate pop-up menu in the Behavior Parameters dialog." & RETURN & RETURN & \
  18.     "For each element you can choose whether animations should continue in the background. " & \
  19.     " This option will tend to slow both the animations and the scrolling process, especially if applied to the arrow buttons." & RETURN & RETURN & \
  20.     "The various sprites will position themselves automatically to the right of the sprite-to-be-scrolled when the movie runs. " & \
  21.     " They revert to their original positions when it stops. " & \
  22.     " To avoid flashes, it would be a good idea to position them by hand in their intended positions." & RETURN & RETURN & \
  23.     "If you use a border on your field members, the scrollbar will sit outside the border. " & \
  24.     " Field box shadows are not recommended. " & \
  25.     " You can always fake external borders and box shadows with shape members (this even gives you a choice of colors)." & RETURN & RETURN & \
  26.     "To make authoring easier, this behavior will continue to work if you change EITHER the sprite channel OR the member in the chosen channel. " & \
  27.     " If you change both, the behavior will no longer know what to scroll." & RETURN & RETURN & \
  28.     "If you do change either the sprite or the member, and then reopen the Behavior Parameters dialog for one of the elements, this will put that behavior out of synch with the others. " & \
  29.     " Simply reopen and close the Parameters dialogs for each of the other elements. " & \
  30.     " If you do not do so, you will receive multiple alerts." & RETURN & RETURN & \
  31.     "This behavior can be used to scroll both editable and non-editable Fields and Text members. " & \
  32.     " For editable members, however, it does not automatically update the dragger position when the length of the text changes, nor does it make the editable member scroll automatically when the user drags the mouse to create a selection." & RETURN & RETURN & \
  33.     "PERMITTED MEMBER TYPES:" & RETURN & \
  34.     "[#animGif, #bitmap, #filmLoop, #flash, #movie, #picture, #shape]" & RETURN & RETURN & \
  35.     "PARAMETERS:" & RETURN & \
  36.     "* Current sprite acts as (up|down arrow | dragger | bar)" & RETURN & \
  37.     "* Scroll the member of <sprite>: <the member of sprite>" & RETURN & \
  38.     "* Standard member (this should not need to be set)" & RETURN & \
  39.     "* Member to display when arrow buttons are pressed" & RETURN & \
  40.     "* Allow animations to continue" & RETURN & RETURN & \
  41.     "PUBLIC METHODS" & RETURN & \
  42.     "=> Scroll the text/field member to a given position" & RETURN & \
  43.     "=> Swap the text/field  member to be scrolled" & RETURN & \
  44.     "=> Get the behavior reference"
  45. end getBehaviorDescription
  46.  
  47.  
  48. on getBehaviorTooltip me
  49.   return \
  50.     "Use 4 graphic members (dragger, bar, up and down arrows)to create a dynamic scrollbar for Text or Field members." & RETURN & RETURN & \
  51.     "Drop this behavior on each sprite separately." & RETURN & RETURN & \
  52.     "Sprites can pass over the scrollbar and animations can continue while the user scrolls." & RETURN & RETURN & \
  53.     "The behavior accepts Lingo calls to:" & RETURN & \
  54.     "+ set the scrolltop of the scrolled member to a given value." & RETURN & \
  55.     "+ swap the current member of the sprite."
  56. end getBehaviorTooltip
  57.  
  58.  
  59.  
  60. -- NOTES FOR DEVELOPERS
  61.  
  62. -- This is the most complex behavior I have written for the Behavior Library.
  63. -- I have included in one script all the handlers necessary to deal with each
  64. -- of the four elements of a scroll bar.
  65. -- 
  66. -- In practice, this one script works as four separate behaviors.  Each element
  67. -- is identified by its myScrollRole (upArrow, downArrow, dragger or bar).  The
  68. -- behavior acts differently for each myScrollRole.  Many handlers are divided
  69. -- into section by a "case myScrollRole of" statement.
  70.  
  71. -- INSTALLATION
  72. -- Initializing the 4 behaviors cannot be done all at once on beginSprite
  73. -- because the behaviors on the other sprites may not exist.  To get round
  74. -- this, I set a myState property to 0 in the StartInstallation handler
  75. -- (on beginSprite).  The first prepareFrame (which is sent once all behaviors
  76. -- in the current frame have been instanciated) sees that "myState + 0 = 0"
  77. -- (or FALSE) and calls the FinishInstallation handler.
  78.  
  79. -- Why "myState + 0"?  Because once the installation is finished, myState is
  80. -- set to #done.  This is a symbol.  A symbol is simply an integer with a
  81. -- special tag.  Adding zero to a symbol gives you access to the integer
  82. -- itself.  Later, when the prepareFrame handler encounters "#done + 0" it
  83. -- evaluates this as a positive integer (or TRUE) and doesn't botherd to
  84. -- reinstall the elements.
  85.  
  86. -- This technique is excessively fast: it slows down the following
  87. -- prepareFrames by something in the order of a millionth of a second.
  88.  
  89. -- Installation itself is a three step-process.
  90.  
  91. -- First: the behavior has to check if the sprite and member that it is to
  92. -- scroll do actually appear where it expects to find them.  The
  93. -- ourScrolledElement property returned by the getPropertyDescriptionList
  94. -- handler is a double-barreled affair: "sprite X:field(member Y of castLib Z)".
  95. -- If sprite X contains a Field or Text member, the behavior assumes that this
  96. -- is the right one, and adopts it as myScrolledMember.  If not, it sets out to
  97. -- look for (member Y of castLib Z), via the FindSprite handler.  If it finds
  98. -- this member in one of the sprites in the frame, then it adopts the new
  99. -- sprite as myScrolledSprite.  If not, it warns the author (4 times, once for
  100. -- each behavior).
  101.  
  102. -- Second: the current behavior has to check if all the others are present.
  103. -- The Initialize handler has already prepared a list of all behaviors which
  104. -- treat the same sprite and/or member: ourControlList.  The CheckControl
  105. -- handler ensures that this list contains one behavior for each control
  106. -- element... and only one.
  107.  
  108. -- Third: the sprite to which the behavior is attached has to be placed
  109. -- correctly beside the sprite it is to scroll.  This is most complex for the
  110. -- Dragger sprite, since it must be placed in accordance with the current
  111. -- scrollTop of myScrolledMember.
  112.  
  113. -- The scrolling itself is always carried out by the behavior attached to the
  114. -- dragger.  The Move and MoveBar handlers (which deal with clicks on the arrow
  115. -- buttons and on the backing bar) end with a...
  116. -- 
  117. --   call (#SetDraggerShift, ourControlList.dragger)
  118. -- 
  119. -- ... command.  The bar behavior must in addition ask the dragger where it now
  120. -- is, using "call (#GetDraggerData, ourControlList.dragger)", so as to update its
  121. -- myActiveZone.
  122.  
  123. -- EXTERNAL LINGO CALLS
  124.  
  125. -- * CustomScrollbar_SetScroll me, theScroll
  126.  
  127. -- If you use standard Lingo to set the scrollTop of myScrolledMember at
  128. -- runtime,  the dragger position will not update until the scroll bar is next
  129. -- used.  Use this call to tell the dragger to do all the work for you
  130.  
  131. -- * CustomScrollbar_SwapMember me, newMember, currentMemberOrSprite
  132.  
  133. -- If you use standard Lingo to swap the member of myScrolledSprite at runtime,
  134. -- the behaviors will continually happily to scroll the member which is now
  135. -- off-stage.  Use this call to tell one of the behaviors do the swapping for
  136. -- you.
  137.  
  138. -- * CustomScrollbar_GetReference me, memberOrSprite, controlOrList
  139.  
  140. -- Using sendAllSprites with one of the above messages will ensure that the
  141. -- job gets done... four times, once by each behavior that makes up the
  142. -- scroll bar.  If you call one of the behaviors directly, the command will be
  143. -- executed just once.  But first you must get an object reference to the
  144. -- behavior in question.  The following syntax is the simplest:
  145. -- 
  146. --   scrollRef = sendAllSprites (#CustomScrollbar_GetReference, sprite X)
  147. -- 
  148. -- This will give you a reference to the behavior on the highest sprite which
  149. -- controls the scrolling of sprite X.
  150.  
  151. -- More details on using these external calls are given in the handlers
  152. -- themselves.
  153.  
  154.  
  155.  
  156. -- HISTORY --
  157.  
  158. -- 27  October 1998: written for the D7 Behaviors Palette by James Newton
  159. -- 24 November 1998: vector shapes abandoned as permitted members
  160. -- 12 january 2000: Added isOKToAttach and removed unneeded error checking.
  161.  
  162.  
  163.  
  164. -- PROPERTIES --
  165.  
  166. property spriteNum
  167. -- error checking
  168. property getPDLError
  169. -- author-defined parameters
  170. property ourScrolledElement -- "<spriteNum>: <member>" of element to scroll
  171. property myScrollRole       -- sprite behaves as upArrow|downArrow|dragger|bar
  172. property myActiveMember     -- member which appears on mouseDown
  173. property myStandardMember   -- member which appears at all other times
  174. property myMultiThreading   -- updates on prepareFrame | while the mouseDown
  175. -- internal properties
  176. property mySprite
  177. property myMember
  178. property myRect              -- rect to which all members must be set to fit
  179. property myScrolledSprite    -- sprite containing myScrolledMember
  180. property myScrolledMember    -- Text or Field member to scroll
  181. property myActiveZone        -- when mouse is in zone, scrolling continues
  182. property myState             -- #paused if mouse is outside myActiveZone
  183. property myNextUpdate        -- slows scrollByPage if necessary
  184. -- properties specific to the dragger
  185. property myZeroLoc           -- loc of dragger when scrollTop = 0
  186. property myDraggerShift      -- vertical distance dwon from myZeroLoc
  187. property myClickV            -- vertical coordinate where the dragger was clicked
  188. property myInterimShift      -- value of shift while the dragger is being scrolled
  189. property myMaxDraggerShift   -- maximum value of dragger shift
  190. -- property specific to the bar
  191. property myPageScroll        -- direction of scroll: #up or #down
  192. -- properties shared between behaviors
  193. property ourControlList      -- list of behaviors which collaborate on scrollbar
  194. property ourMaxScroll        -- list shared by behaviors: [maximum scrollTop]
  195.  
  196.  
  197.  
  198.  
  199. -- EVENT HANDLERS --
  200.  
  201. on resolve (prop)
  202.   case prop of
  203.     myScrollRole:  
  204.       choicesList = ["upArrow", "downArrow", "dragger", "bar"]
  205.       lookup = [#upArrow, #downArrow, #dragger, #bar]
  206.     otherwise: nothing
  207.   end case
  208.   return lookup[findPos(choicesList, prop)]
  209. end resolve
  210.  
  211. on beginSprite me
  212.   myScrollRole = resolve (myScrollRole)
  213.   StartInstallation me
  214. end beginSprite
  215.  
  216.  
  217. on prepareFrame me
  218.   if not myState + 0 then FinishInstallation me -- First prepareFrame only
  219.   if myMultiThreading then UpdateScroll me
  220. end prepareFrame
  221.  
  222.  
  223. on mouseDown me
  224.   StartScroll me
  225. end mouseDown
  226.  
  227. on mouseUp me
  228.   EndScroll me
  229. end mouseUp
  230.  
  231. on mouseUpOutside me
  232.   EndScroll me
  233. end mouseUpOutside
  234.  
  235.  
  236.  
  237. -- CUSTOM HANDLERS --
  238.  
  239. on StartInstallation me -- sent by beginSprite
  240.   mySprite         = sprite(me.spriteNum)
  241.   myMember         = mySprite.member
  242.   memberType       = myMember.type
  243.   
  244.   -- Error checking
  245.   if voidP (myScrollRole) then
  246.     ErrorAlert (me, #getPDL_Invalid)
  247.   end if
  248.   
  249.   -- The member of the sprite may have changed since getPDL
  250.   myScrolledSprite = value (ourScrolledElement)
  251.   myScrolledMember = myScrolledSprite.member
  252.   
  253.   -- Ensure that the scrolled sprite has not changed or moved
  254.   memberType = myScrolledMember.type
  255.   case memberType of
  256.     #field, #text: -- do nothing
  257.     otherwise
  258.       -- Try to find the expected member in another sprite
  259.       FindSprite (me)
  260.   end case
  261.   -- End of error checking
  262.   
  263.   -- Calculate ideal sprite.rect
  264.   topLeft = point (myScrolledSprite.left, myScrolledSprite.top)
  265.   myRect  = myScrolledSprite.rect - rect (topLeft, topLeft)
  266.   if memberType = #field then
  267.     if myScrolledMember.border then
  268.       myRect[1] = 1
  269.     end if
  270.   end if
  271.   -- Contact other sprites with the same behavior
  272.   ourMaxScroll = []
  273.   set ourControlList = [:]
  274.   sendAllSprites \
  275. (\
  276.  #CustomScrollbar_RollCall, \
  277.  ourScrolledElement, \
  278.  ourControlList, \
  279.  ourMaxScroll \
  280. )
  281.   myState = 0 -- Check will be run on first prepareFrame
  282. end StartInstallation
  283.  
  284.  
  285. on FindSprite me -- sent by StartInstallation
  286.   -- Finds highest sprite containing the expected member to scroll
  287.   saveDelimiter = the itemDelimiter
  288.   the itemDelimiter = ":"
  289.   memberData = ourScrolledElement.item[2]
  290.   the itemDelimiter = saveDelimiter
  291.   delete memberData.word[1] -- Remove type information
  292.   delete memberData.char[1] -- Remove remaining space
  293.   memberData = value (memberData)
  294.   myScrolledMember = member (memberData)
  295.   
  296.   scrollSprite = the lastChannel
  297.   repeat while scrollSprite
  298.     if sprite(scrollSprite).member = myScrolledMember then
  299.       -- We've found it!
  300.       myScrolledSprite = sprite (scrollSprite)
  301.       exit
  302.     end if
  303.     scrollSprite = scrollSprite - 1
  304.   end repeat
  305.   if not scrollSprite then
  306.     -- The expected member was not found
  307.     ErrorAlert me, #notScrollable, myScrolledSprite.member.type
  308.   end if
  309. end FindSprite
  310.  
  311.  
  312. on FinishInstallation me -- sent by first prepareFrame
  313.   -- Terminates the initialization process once all behaviors are in place
  314.   CheckControlList me
  315.   -- Place this sprite to the right of myScrolledSprite
  316.   InstallElement me
  317. end FinishInstallation
  318.  
  319.  
  320. on CheckControlList me -- sent by FinishInstallation
  321.   -- Checks if ourControlList contains one-and-only-one behavior with each role
  322.   checkList = duplicate (ourControlList)
  323.   roleList  = [#upArrow, #downArrow, #dragger, #bar]
  324.   i = roleList.count()
  325.   repeat while i
  326.     theRole = roleList[i]
  327.     rolePosition = checkList.findPos (theRole)
  328.     if rolePosition then
  329.       roleList.deleteAt(i)
  330.       checkList.deleteAt (rolePosition)
  331.     end if
  332.     i = i - 1
  333.   end repeat
  334.   if checkList.count() then
  335.     ErrorAlert me, #extraControl, checkList.getPropAt (1)   
  336.   end if
  337.   if roleList.count() then
  338.     ErrorAlert me, #missingControls, roleList 
  339.   end if
  340. end CheckControlList
  341.  
  342.  
  343. on InstallElement me
  344.   -- sent by FinishInstallation on first prepareFrame, _SwapMembers
  345.   -- Places sprite next to myScrolledSprite and determines myActiveZone  
  346.   scrollRoof  = myScrolledSprite.top
  347.   scrollFloor = myScrolledSprite.bottom
  348.   scrollLeft  = myScrolledSprite.right
  349.   if myScrolledMember.type = #field then
  350.     scrollLeft = scrollLeft - (myScrolledMember.border <> 0)
  351.   end if
  352.   
  353.   case myScrollRole of
  354.     #upArrow: 
  355.       mySprite.loc = point (scrollLeft, scrollRoof) + myMember.regPoint
  356.       myActiveZone = mySprite.rect
  357.     #downArrow:
  358.       locAdjust    = myMember.regPoint - [0, mySprite.height]
  359.       rectAdjust   = mySprite.rect - rect (mySprite.loc, mySprite.loc) ---òòò
  360.       mySprite.loc = point (scrollLeft, scrollFloor) + locAdjust
  361.       -- myActiveZone = mySprite.rect ùùùòòò
  362.       myActiveZone = rectAdjust + rect (mySprite.loc, mySprite.loc)
  363.     #bar:
  364.       barRoof      = scrollRoof  + call (#SpriteHeight, ourControlList.upArrow)
  365.       barRight     = scrollLeft  + call (#SpriteWidth,  ourControlList.upArrow)
  366.       barFloor     = scrollFloor - call (#SpriteHeight,ourControlList.downArrow)
  367.       mySprite.rect= rect (scrollLeft, barRoof,    barRight, barFloor)
  368.     #dragger:
  369.       case myMember.type of
  370.         #vectorShape:
  371.           regOffset = (myMember.strokeWidth  + 1) / 2
  372.           myMember.regPoint = point (regOffset, regOffset)
  373.         otherwise
  374.           myMember.regPoint = point (0,0)
  375.       end case
  376.       
  377.       upHeight        = call (#SpriteHeight, ourControlList.upArrow)
  378.       upWidth         = call (#SpriteHeight, ourControlList.upArrow)
  379.       downHeight      = call (#SpriteHeight, ourControlList.downArrow)
  380.       
  381.       draggerRoof     = scrollRoof  + upHeight
  382.       myZeroLoc       = point (scrollLeft, draggerRoof)
  383.       barRight        = scrollLeft  + upWidth
  384.       myActiveZone    = rect (scrollLeft, scrollRoof, barRight, scrollFloor)
  385.       myActiveZone    = inflate (myActiveZone, 32, 32)
  386.       
  387.       arrowAdjust     = upHeight + downHeight
  388.       pageHeight      = myScrolledSprite.height
  389.       scrollHeight    = pageHeight - arrowAdjust
  390.       draggerHeight   = mySprite.Height
  391.       
  392.       myMaxDraggerShift = 0 -- pre-emptive measure, in case dragger can't scroll
  393.       if scrollHeight < 0 then
  394.         -- Adjust length of myScrolledMember so that arrow buttons fit
  395.         minRect               = myScrolledMember.rect
  396.         minRect[4]            = arrowAdjust
  397.         myScrolledMember.rect = minRect
  398.         call (#InstallElement, ourControlList.bar)
  399.       end if
  400.       if scrollHeight < draggerHeight then
  401.         mySprite.loc = point (-999, -999)
  402.       else
  403.         -- Dragger can slide: set myMaxDraggerShift to reflect this
  404.         if myScrolledMember.type = #field then
  405.           theMargin     = myScrolledMember.margin
  406.           theMargin     = theMargin - theMargin mod 2
  407.           borderAdjust  = (myScrolledMember.border * 2) + theMargin
  408.         else
  409.           borderAdjust  = 0
  410.         end if
  411.         myMaxDraggerShift = scrollHeight - draggerHeight + borderAdjust
  412.       end if
  413.       
  414.       ourMaxScroll[1] = GetMaxScroll (me)
  415.       SetDraggerShift me
  416.   end case
  417.   myState = #done
  418. end InstallElement
  419.  
  420.  
  421. on GetMaxScroll me
  422.   pageHeight = myScrolledSprite.height
  423.   
  424.   if myScrolledMember.type = #text then
  425.     lastChar   = myScrolledMember.char.count
  426.     textHeight = charPostoloc  (myScrolledMember, lastChar)[2]
  427.   else
  428.     textHeight = myScrolledMember.height
  429.   end if
  430.   
  431.   maxScroll  = textHeight - pageHeight
  432.   if maxScroll < 1 then
  433.     return 0
  434.   else
  435.     return maxScroll
  436.   end if
  437. end GetMaxScroll
  438.  
  439.  
  440.  
  441. -- SCROLLING --
  442.  
  443. on StartScroll me -- sent by mouseDown
  444.   -- StartInstallations properties for scroll and either performs one update
  445.   -- (if myMultiThreading is TRUE) or completes the operation (in repeat loop).
  446.   myState = #active
  447.   ourMaxScroll[1] = GetMaxScroll (me)
  448.   case myScrollRole of
  449.     #dragger:
  450.       -- Determine start of scroll
  451.       myClickV        = the clickLoc[2]
  452.       myInterimShift = 0
  453.     #upArrow, #downArrow:
  454.       -- Swap members
  455.       mySprite.Member = myActiveMember
  456.     #bar:
  457.       -- Determine active zone
  458.       draggerData = call (#GetDraggerData, ourControlList.dragger)
  459.       if the clickLoc[2] < draggerData.top then
  460.         myActiveZone = GetBarZone (me, draggerData, #up)
  461.       else
  462.         myActiveZone = GetBarZone (me, draggerData, #down)
  463.       end if
  464.   end case
  465.   UpdateScroll me
  466.   if myMultiThreading then exit
  467.   
  468.   -- Use a tight repeat loop to scroll as fast as possible
  469.   repeat while the mouseDown
  470.     UpdateScroll me
  471.   end repeat
  472. end StartScroll
  473.  
  474.  
  475. on UpdateScroll me -- sent by prepareFrame, StartScroll, ResumeScroll
  476.   -- Determines whether to pause or to scroll (and how to)
  477.   case myState of
  478.     #active:
  479.       if inside (the mouseLoc, myActiveZone) then
  480.         case myScrollRole of
  481.           #dragger:   MoveDragger me
  482.           #upArrow:   Move me, TRUE
  483.           #downArrow: Move me, FALSE
  484.           #bar:       MoveBar me
  485.         end case
  486.       else
  487.         PauseScroll me
  488.       end if
  489.     #paused:
  490.       ResumeScroll me
  491.   end case
  492. end UpdateScroll
  493.  
  494.  
  495. on GetBarZone me, draggerData, pageScroll
  496.   -- Determines zone in which the mouse makes the text scroll by page
  497.   if not voidP (pageScroll) then
  498.     myPageScroll  = pageScroll
  499.   end if
  500.   barZone       = mySprite.rect
  501.   if myPageScroll = #up then
  502.     barZone [2] = myScrolledSprite.top
  503.     barZone [4] = draggerData.top
  504.   else 
  505.     barZone [2] = draggerData.bottom
  506.     barZone [4] = myScrolledSprite.bottom
  507.   end if
  508.   return barZone
  509. end GetBarZone
  510.  
  511.  
  512. on MoveDragger me
  513.   -- Checks if the dragger has moved, and if so modifies scroll
  514.   newScroll = myDraggerShift + the mouseV - myClickV
  515.   newScroll = max (0, min (newScroll, myMaxDraggerShift))
  516.   if myInterimShift = newScroll then exit
  517.   
  518.   myInterimShift = newScroll      
  519.   SetTextScroll me, myInterimShift
  520. end MoveDragger
  521.  
  522.  
  523. on Move me, up
  524.   -- Scrolls one line up or down
  525.   if the ticks < myNextUpdate then exit
  526.   if voidP (myNextUpdate) then
  527.     -- Allow time for a quick single-line click
  528.     myNextUpdate = the ticks + 10
  529.   else
  530.     -- Don't scroll too fast
  531.     myNextUpdate = the ticks + 1
  532.   end if
  533.   
  534.   if up then
  535.     if not myScrolledMember.scrollTop then exit
  536.     scrollByLine myScrolledMember, -1
  537.   else
  538.     maxScroll = ourMaxScroll[1]
  539.     if myScrolledMember.scrollTop = maxScroll then exit
  540.     scrollByLine myScrolledMember, 1
  541.     if myScrolledMember.scrollTop > maxScroll then
  542.       myScrolledMember.scrollTop = maxScroll
  543.     end if
  544.   end if
  545.   call (#SetDraggerShift, ourControlList.dragger)
  546. end MoveUp
  547.  
  548.  
  549. on MoveBar me, up
  550.   -- Scrolls one page up or down, leaving one previous line visible
  551.   if the ticks < myNextUpdate then exit
  552.   myNextUpdate = the ticks + 10 -- So as not to scroll too fast
  553.   
  554.   if myPageScroll = #up then
  555.     scrollByPage (myScrolledMember, -1)
  556.     if myScrolledMember.scrollTop then
  557.       -- Leave previous top line visible at bottom
  558.       scrollByLine (myScrolledMember,  1)
  559.     end if
  560.   else -- down
  561.     maxScroll = ourMaxScroll[1]
  562.     if myScrolledMember.scrollTop = maxScroll then exit
  563.     scrollByPage (myScrolledMember,  1)
  564.     -- Leave previous bottom line visible at top
  565.     scrollByLine (myScrolledMember, -1)
  566.     
  567.     if myScrolledMember.scrollTop > maxScroll then
  568.       myScrolledMember.scrollTop = maxScroll
  569.     end if
  570.   end if
  571.   call (#SetDraggerShift, ourControlList.dragger)
  572.   -- The area of the bar where the mouse should active has just got smaller
  573.   draggerData  = call (#GetDraggerData, ourControlList.dragger)
  574.   myActiveZone = GetBarZone (me, draggerData)
  575. end MoveBar
  576.  
  577.  
  578. on PauseScroll me
  579.   -- Pauses the behavior when the mouse leaves myActiveZone
  580.   myState = #paused
  581.   case myScrollRole of
  582.     #dragger:
  583.       -- Revert to original scroll
  584.       SetTextScroll me, myDraggerShift
  585.     #upArrow, #downArrow:
  586.       -- Revert to original button image
  587.       mySprite.member = myStandardMember
  588.       if not myMultiThreading then
  589.         updateStage
  590.       end if
  591.   end case
  592. end PauseScroll
  593.  
  594.  
  595. on ResumeScroll me
  596.   -- Reactivates the behavior when the mouse returns to myActiveZone
  597.   myState = #active
  598.   case myScrollRole of
  599.     #upArrow, #downArrow:
  600.       mySprite.member = myActiveMember
  601.   end case
  602.   UpdateScroll me
  603. end ResumeScroll
  604.  
  605.  
  606. on EndScroll me
  607.   -- Tidies up after scroll is over, ready for the next one
  608.   case myScrollRole of
  609.     #dragger:
  610.       if not voidP (myInterimShift) then
  611.         if ourMaxScroll[1] then
  612.           myDraggerShift = myInterimShift
  613.         else
  614.           myDraggerShift = 0
  615.           ShiftDragger me, myDraggerShift
  616.         end if
  617.       end if
  618.       myInterimShift = void
  619.     #upArrow, #downArrow:
  620.       mySprite.member = myStandardMember
  621.       myNextUpdate = void
  622.   end case
  623.   myState = #done
  624. end EndScroll
  625.  
  626.  
  627. on SetTextScroll me, draggerShift
  628.   -- Calculates the scrollTop of the text, then sets it and moves the thum
  629.   if ourMaxScroll[1] then
  630.     textScroll = (draggerShift * ourMaxScroll[1]) / myMaxDraggerShift
  631.     myScrolledMember.scrollTop = textScroll
  632.   else
  633.     -- draggerShift = 0
  634.   end if
  635.   
  636.   ShiftDragger me, draggerShift
  637. end SetTextScroll
  638.  
  639.  
  640. on SetDraggerShift me
  641.   -- Calculates the position of the dragger from myScrolledMember.scrollTop
  642.   maxScroll = ourMaxScroll[1]
  643.   if maxScroll then
  644.     textScroll = myScrolledMember.scrollTop
  645.     myDraggerShift = (textScroll * myMaxDraggerShift) / maxScroll
  646.   else
  647.     myDraggerShift = 0
  648.   end if
  649.   
  650.   ShiftDragger me, myDraggerShift
  651. end SetDraggerShift
  652.  
  653.  
  654. on ShiftDragger me, draggerShift
  655.   if not myMaxDraggerShift then
  656.     -- Don't move dragger if there is no space to move it
  657.     exit
  658.   end if
  659.   
  660.   mySprite.loc = myZeroLoc + [0, draggerShift]
  661.   updateStage
  662. end ShiftDragger
  663.  
  664.  
  665.  
  666. -- PUBLIC METHODS (responses to #sendSprite, #sendAllSprites, #call) --
  667.  
  668. on CustomScrollbar_SetScroll me, theScroll
  669.   -- Allows an editable field to update its scrolltop via the #dragger behavior
  670.   
  671.   -- Error check
  672.   case ilk (theScroll) of
  673.     #integer: -- nothing
  674.     #float:   theScroll = integer (theScroll)
  675.     otherwise
  676.       return #invalidTypeError
  677.   end case
  678.   -- End of error check
  679.   
  680.   theScroll = max (0, min (theScroll, ourMaxScroll[1]))
  681.   myScrolledMember.scrollTop = theScroll
  682.   call (#SetDraggerShift, ourControlList.dragger, theScroll)
  683. end CustomScrollbar_SetScroll
  684.  
  685.  
  686. on CustomScrollbar_SwapMember me, newMember, currentMemberOrSprite
  687.   -- Allows you to change the member of the scrolled sprite at runtime
  688.   -- newMember is a member reference, name or number.  If you have a number of
  689.   -- custom scrollbars in the frame, then currentMemberOrSprite identifies 
  690.   -- which member to change if you use a sendAllSprites messsage.
  691.   -- You can leave the currentMemberOrSprite parameter empty if you call a
  692.   -- specific behavior.
  693.   --
  694.   -- The following examples both set the member of sprite 1 to member 2.
  695.   -- The first example will actually do this four times, once for each
  696.   -- behavior that makes up the scrollbar:
  697.   --
  698.   --   sendAllSprites (#CustomScrollbar_SwapMember, member 2, sprite 1)
  699.   -- OR
  700.   --   scrollBehavior = sendAllSprites (#CustomScrollbar_GetReference, sprite 1)
  701.   --   call (#CustomScrollbar_SwapMember, scrollBehavior, member 2)
  702.   
  703.   -- Error check
  704.   case ilk (newMember) of
  705.     #member: -- do nothing
  706.     #integer, #string:   
  707.       memberExists = (the number of member (newMember) > 0)
  708.       if memberExists then
  709.         newMember = member (newMember)
  710.       else
  711.         return #memberInexistant
  712.       end if
  713.     otherwise
  714.       return #invalidType
  715.   end case
  716.   case newMember.type of
  717.     #text, #field: -- do nothing
  718.     otherwise
  719.       return #invalidMemberType
  720.   end case
  721.   -- End of error check
  722.   
  723.   case ilk (currentMemberOrSprite) of
  724.     #sprite: if currentMemberOrSprite <> myScrolledSprite then exit  
  725.     #member: if currentMemberOrSprite <> myScrolledMember then exit
  726.       -- otherwise ignore currentMemberOrSprite
  727.   end case
  728.   
  729.   myScrolledMember = newMember
  730.   idealRect        = myRect
  731.   if myScrolledMember.type = #field then
  732.     hBorder        = myScrolledMember.border
  733.     vBorder        = hBorder - (hBorder <> 0)
  734.     hMargin        = myScrolledMember.margin
  735.     vMargin        = hMargin / 2   --- hMargin - (hMargin mod 2)
  736.     idealRect      =inflate (idealRect, -hBorder-hMargin, -hBorder-vMargin)
  737.     if myRect[1] then -- 0riginal meber had a border...
  738.       if hBorder then -- ... and so does this member
  739.         idealRect[1] = idealRect[1] - 1
  740.       end if
  741.     else -- Original member had NO border...
  742.       if hBorder then -- ... but this one has.
  743.         idealRect[1] = idealRect[1] - 1
  744.       end if
  745.     end if
  746.   end if
  747.   
  748.   myScrolledMember.rect   = idealRect
  749.   myScrolledSprite.member = myScrolledMember
  750.   myScrolledMember.boxType = #fixed
  751.   
  752.   i = ourControlList.count()
  753.   repeat while i
  754.     call (#NewMember, ourControlList[i])
  755.     i = i - 1
  756.   end repeat
  757.   updateStage
  758.   call (#InstallElement, ourControlList.dragger)
  759. end CustomScrollbar_SwapMember
  760.  
  761.  
  762. on CustomScrollbar_GetReference me, memberOrSprite, controlOrList
  763.   -- Returns a reference to the behavior for Lingo calls.
  764.   -- The parameters are optional: use them to find a specific behavior.
  765.   -- Use 'theElement' to find the scrollbar of a particular sprite or member
  766.   -- Use 'controlOrList' to find a specific behavior (up|downArrow|dragger|bar)
  767.   -- or to return a property list of all behaviors.
  768.   -- Examples:
  769.   --
  770.   -- put sendAllSprites (#CustomScrollbar_GetReference, sprite 1, #dragger)
  771.   -- -- <offspring "Custom Scrollbar" 3 486bc20>
  772.   --
  773.   -- put sendAllSprites (#CustomScrollbar_GetReference, sprite 1, [:])
  774.   -- -- #bar: <ref>, #upArrow: <ref>, #dragger: <ref>, #downArrow: <ref>]
  775.   --
  776.   -- You can leave the controlOrList parameter empty.  If you do, the
  777.   -- behavior reference on the highest sprite in the scrollbar will be returned.
  778.   
  779.   case ilk (memberOrSprite) of
  780.     #sprite: if memberOrSprite <> myScrolledSprite then exit  
  781.     #member: if memberOrSprite <> myScrolledMember then exit
  782.     otherwise
  783.       exit
  784.   end case
  785.   
  786.   if not voidP (controlOrList) then
  787.     if ilk (controlOrList) = #propList then
  788.       controlOrList.addProp(myScrollRole, me)
  789.       return controlOrList
  790.     else
  791.       if controlOrList = myScrollRole then return me
  792.     end if
  793.   else
  794.     return me
  795.   end if
  796. end CustomScrollbar_GetReference
  797.  
  798.  
  799.  
  800. -- INTER-SPRITE COMMUNICATION --
  801. -- (responses to #sendAllSprites) --
  802.  
  803. on CustomScrollbar_RollCall me, scrolledElement, controlList, maxScrollList
  804.   -- sent on StartInstallation by this and other sprites with the same behavior
  805.   if scrolledElement <> ourScrolledElement then exit
  806.   
  807.   ourControlList = controlList
  808.   ourControlList.addProp(myScrollRole, me)
  809.   ourMaxScroll   = maxScrollList
  810. end CustomScrollbar_RollCall
  811.  
  812. -- (responses to #call) --
  813.  
  814. on SpriteHeight me
  815.   -- Called by InstallElement in behaviors on the bar and the dragger
  816.   -- Dealt with by behavior on the upArrow
  817.   return mySprite.height
  818. end
  819.  
  820.  
  821. on SpriteWidth me
  822.   -- Called by InstallElement in behaviors on the bar and the dragger
  823.   -- Dealt with by behavior on the upArrow
  824.   return mySprite.width
  825. end
  826.  
  827.  
  828. on GetDraggerData me
  829.   -- Called by  StartScroll, MoveBar in behavior on the bar
  830.   -- Dealt with by behavior on the dragger
  831.   return [#top: mySprite.top, #bottom: mySprite.bottom]
  832. end
  833.  
  834.  
  835. on NewMember me -- sent by _SwapMember from the behavior that received the call
  836.   myScrolledMember = myScrolledSprite.member
  837. end NewMember
  838.  
  839.  
  840.  
  841. -- ERROR CHECKING --
  842.  
  843. on ErrorAlert me, theError, data
  844.   -- sent by getPropertyDescriptionList, StartInstallation
  845.   case theError of
  846.       
  847.     #noScrollableSprites:
  848.       alert "Error: Please place a Field or Text member on the Stage before using this behavior to create a scroll bar." & RETURN & \
  849.     "      " & RETURN & \
  850.     "Hit OK and then delete this behavior from the sprite." & RETURN & \
  851.     "      " & RETURN & \
  852.     "For more information on deleting Behaviors, see the Help system." 
  853.     otherwise:
  854.       -- Determine the behavior's name
  855.       behaviorName = string (me)
  856.       delete word 1 of behaviorName
  857.       delete the last word of behaviorName
  858.       delete the last word of behaviorName
  859.       -- Convert #data to useful value
  860.       case data.ilk of
  861.         #void: data = "<void>"
  862.         #symbol: data = "#"&data
  863.       end case
  864.       if theError <> #getPDL_Invalid then
  865.         -- Determine the name and type of myScrolledMember
  866.         memberName = myScrolledMember.name
  867.         if memberName = EMPTY then
  868.           memberName = myScrolledMember
  869.         else
  870.           memberName = QUOTE&memberName"E
  871.         end if
  872.         memberName = myScrolledMember.type&&memberName
  873.       end if
  874.       
  875.       case theError of
  876.         #getPDL_Invalid:
  877.           message = substituteStrings(me, \
  878. "BEHAVIOR ERROR: Frame ^0, Sprite ^1" & RETURN & \
  879.     "          " & RETURN & \
  880.     "Parameters for the ^2 behavior have not been set." & RETURN & \
  881.     "          " & RETURN & \
  882.     "Please reopen the Behavior Parameters dialog and choose again.", \
  883. ["^0": the frame, "^1": me.spriteNum, "^2": behaviorName])
  884.           alert message
  885.           halt
  886.         #notScrollable:
  887.           message = substituteStrings(me, \
  888. "BEHAVIOR ERROR: Frame ^0, Sprite ^1" & RETURN & \
  889.     "Behavior ^2" & RETURN & \
  890.     "          " & RETURN & \
  891.     "Sprite ^3 does not contain a Field or Text member." & RETURN & RETURN & \
  892.     "Choose again in the Behavior Parameters dialog." & RETURN & RETURN & \
  893.     "Member type = ^4", \
  894. ["^0": the frame, "^1": me.spriteNum, "^2": behaviorName, "^3": myScrolledSprite.spriteNum, \
  895. "^4": data])
  896.           alert message
  897.           halt
  898.         #extraControl:
  899.           if the runMode = "Author" then
  900.             message = substituteStrings(me, \
  901. "BEHAVIOR ERROR: Frame ^0, Sprite ^1" & RETURN & \
  902.     "Behavior ^2" & RETURN & \
  903.     "          " & RETURN & \
  904.     "There is more than one ^3 sprite defined for the scroll bar for sprite ^4, ^5.", \
  905. ["^0": the frame, "^1": me.spriteNum, "^2": behaviorName, "^3": data, \
  906. "^4": myScrolledSprite.spriteNum, "^5": memberName])       
  907.             alert message
  908.             halt
  909.           end if
  910.         #missingControls:
  911.           message = substituteStrings(me, \
  912. "BEHAVIOR ERROR: Frame ^0, Sprite ^1" & RETURN & \
  913.     "Behavior ^2" & RETURN & \
  914.     "          " & RETURN & \
  915.     "The following elements of the scroll bar for sprite ^3, ^4 are missing:" & RETURN & \
  916.     "          " & RETURN & \
  917.     "^5", \
  918. ["^0": the frame, "^1": me.spriteNum, "^2": behaviorName, "^3": myScrolledSprite.spriteNum, \
  919. "^4": memberName, "^5": data])
  920.           alert message
  921.           halt
  922.       end case
  923.   end case
  924. end ErrorAlert
  925.  
  926.  
  927. on substituteStrings(me, parentString, childStringList) --------------
  928.   -- 
  929.   -- * Modifies parentString so that the strings which appear as
  930.   --   properties in childStringList are replaced by the values
  931.   --   associated with those properties.
  932.   --
  933.   -- <childStringList> has the format ["^1": "replacement string"]
  934.   --------------------------------------------------------------------
  935.   
  936.   i = childStringList.count()
  937.   repeat while i
  938.     tempString = ""
  939.     dummyString  = childStringList.getPropAt(i)
  940.     replacement  = childStringList[i]
  941.     lengthAdjust = dummyString.char.count - 1
  942.     repeat while TRUE
  943.       position = offset(dummyString, parentString)
  944.       if not position then
  945.         parentString = tempString&parentString
  946.         exit repeat
  947.       else
  948.         if position <> 1 then
  949.           tempString = tempString&parentString.char[1..position - 1]
  950.         end if
  951.         tempString = tempString&replacement
  952.         delete parentString.char[1..position + lengthAdjust]
  953.       end if
  954.     end repeat
  955.     i = i - 1
  956.   end repeat
  957.   
  958.   return parentString
  959. end substituteStrings 
  960.  
  961.  
  962. -- AUTHOR-DEFINED PARAMETERS --
  963.  
  964. on isOKToAttach (me, aSpriteType, aSpriteNum)
  965.   
  966.   case aSpriteType of
  967.     #graphic:
  968.       return getPos([#animgif, #bitmap, #filmLoop, #flash, #movie, #picture, #shape], sprite(aSpriteNum).member.type) <> 0
  969.     #script:
  970.       return FALSE
  971.   end case
  972.   
  973. end
  974.  
  975.  
  976. on getPropertyDescriptionList me
  977.   
  978.   if not the currentSpriteNum then exit
  979.   
  980.   -- Error check: does current sprite contain appropriate member type?
  981.   theMember = sprite(the currentSpriteNum).member
  982.   memberType = theMember.type
  983.   -- Find sprites with field or text members
  984.   scrollableList = GetScrollableSprites (me)
  985.   if not scrollableList.count then
  986.     return ErrorAlert (me, #noScrollableSprites)
  987.   end if
  988.   
  989.   return \
  990. [ \
  991.  #myScrollRole: \
  992.  [ \
  993.   #comment: "Current sprite acts as", \
  994.   #format:  #string, \
  995.   #range:  ["upArrow", "downArrow", "dragger", "bar"], \
  996.   #default:  1 \
  997.  ], \
  998.  #ourScrolledElement: \
  999.  [ \
  1000.   #comment: "Scroll the member of", \
  1001.   #format:  #string, \
  1002.   #range:    scrollableList, \
  1003.   #default:  scrollableList[1] \
  1004.  ], \
  1005.  #myStandardMember: \
  1006.  [ \
  1007.   #comment: "Standard member", \
  1008.   #format:  #graphic, \
  1009.   #default:  theMember \
  1010.  ], \
  1011.  #myActiveMember: \
  1012.  [ \
  1013.   #comment: "(Arrows only) mouseDown member", \
  1014.   #format:  #graphic, \
  1015.   #default:  theMember \
  1016.  ], \
  1017.  #myMultiThreading: \
  1018.  [ \
  1019.   #comment: "Allow animations to continue (slower)?", \
  1020.   #format:  #boolean, \
  1021.   #default:  TRUE \
  1022.  ] \
  1023. ]
  1024. end getPropertyDescriptionList
  1025.  
  1026.  
  1027. on GetScrollableSprites me, permittedTypes
  1028.   -- Returns a list of sprites containing Field or Text members, in the format:
  1029.   -- ["<spriteNumber>, <type> <member.name|member>",...]
  1030.   
  1031.   scrollableSprites = []
  1032.   repeat with theSprite = 1 to the lastChannel
  1033.     theMember = sprite(theSprite).member
  1034.     case theMember.type of
  1035.       #field, #text:
  1036.         memberName = theMember.name
  1037.         if memberName = EMPTY then
  1038.           memberName = theMember
  1039.         else
  1040.           memberName = QUOTE&memberName"E
  1041.         end if
  1042.         memberName = theMember.type&&memberName
  1043.         scrollableSprites.append ("sprite "&theSprite&": "&memberName)
  1044.     end case
  1045.   end repeat
  1046.   return scrollableSprites
  1047. end GetScrollableSprites